The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
Build.PL 107
Changes 062
MANIFEST 11
MANIFEST.SKIP 02
META.yml 1819
SIGNATURE 420
check_max.c 13182
inc/Local/Module/Build.pm 18162
lib/Time/y2038/Everywhere.pm 11
lib/Time/y2038.pm 11
munge_config 73
t/everywhere.t 20
t/time.t 1180
y2038/time64.c 36239
y2038/time64.h 417
y2038/time64_config.h.in 1725
y2038/time64_limits.h.in 088
17 files changed (This is a version diff) 181889
@@ -19,20 +19,17 @@ my $build = Local::Module::Build->new(
 
 #    extra_compiler_flags => "-g -Wall -ansi -pedantic -Wno-long-long -Wextra -Wdeclaration-after-statement -Wendif-labels -Wconversion",
 
-    PL_files => {
-        'munge_config'  => 'y2038/time64_config.h'
-    },
-
-    configure_requires  => {
-        'Module::Build'         => '0.2808',
-    },
-
     build_requires      => {
-        'Module::Build'         => '0.2808',
+        'Module::Build'         => '0.36',
         'Test::Warn'            => '0.11',
         'Test::Exception'       => '0.27',
-        'Test::More'            => 0.62,
+        'Test::More'            => 0.82,
         'ExtUtils::CBuilder'    => 0.24,
+        'JSON'                  => 2.17,
+    },
+
+    configure_requires => {
+        'Module::Build'         => '0.36'
     },
 
     requires            => {
@@ -1,3 +1,65 @@
+20100218  Thu Feb 18 12:29:49 PST 2010
+  * No changes since the last alpha
+
+  Summary of Changes Since The Last Stable
+
+  Improvements
+  * System mktime() is now probed giving more accurate
+    mktime() on 64 bit systems.
+
+  Test
+  * Test fixes for 64 bit machines, OS X, BSD, people in the UK
+
+  Build
+  * Build fixes Versions of Visual C++ lacking long long
+  * Build fixes for Strawberry and any system with a time.h
+    limit of 0.
+  * Build fixes for slightly out of date Module::Build
+
+
+20100216.1421_04  Tue Feb 16 14:22:08 PST 2010
+  Build
+  * Switch dependency on JSON::XS to just JSON to eliminate
+    Lehmann-ware dependency.
+  * The test for the limits of the time.h did not work for
+    finding functions.
+  * Work around bug in OS X 10.6 where gmtime() would be wrong
+    before -70546986201600.
+
+  Test
+  * Add some more fuzz around the future localtime() tests.
+
+
+20100214.1823_03  Sun Feb 14 18:23:22 PST 2010
+  Build
+  * Bump up the minimum Module::Build release to fix a bug where
+    Build would not see inc/ and thus couldn't find
+    Local::Module::Build.
+
+
+20100213.0504_02  Sat Feb 13 05:04:39 PST 2010
+  Build
+  * The check for the minimum time and date was broken and would only
+    do a single check.
+
+
+20100213.0000_01  Sat Feb 13 04:32:59 PST 2010
+  Improvements
+  * mktime() and timelocal() will now make better use of the system
+    functions giving better accuracy.
+
+  Build
+  * Had too old a version of Module::Build listed as the requirement.
+  * The limit check program was ignoring a limit of 0.  Duh.
+  * Fix so the check_max program is not always re-run on Cygwin.
+
+  Tests
+  * More localtime() tests to track down the 64 bit issues
+  * Failures on 64 bit systems, especially in the UK, should
+    be fixed
+  * More sanity tests for distant dates
+
+
 20081111  Tue Nov 11 15:36:27 PST 2008
   Build Improvements
   * Moved building the program to check the limits of time.h to the code
@@ -17,4 +17,4 @@ typemap
 y2038/time64.c
 y2038/time64.h
 y2038/time64_config.h.in
-SIGNATURE    Added here by Module::Build
+y2038/time64_limits.h.in
@@ -44,6 +44,8 @@
 check_max$
 lib/Time/y2038\.c
 y2038/time64_config.h$
+y2038/time64_limits.h$
 
 # Avoid our own dists
 ^Time-y2038-
+^MYMETA.yml$
@@ -1,30 +1,31 @@
 ---
-name: Time-y2038
-version: 20081111
+abstract: "Versions of Perl's time functions which work beyond 2038"
 author:
   - 'Michael G Schwern <schwern@pobox.com>'
-abstract: Versions of Perl's time functions which work beyond 2038
-license: perl
-resources:
-  license: http://dev.perl.org/licenses/
-configure_requires:
-  Module::Build: 0.2808
-requires:
-  perl: 5.6.1
 build_requires:
   ExtUtils::CBuilder: 0.24
-  Module::Build: 0.2808
+  JSON: 2.17
+  Module::Build: 0.36
   Test::Exception: 0.27
-  Test::More: 0.62
+  Test::More: 0.82
   Test::Warn: 0.11
+configure_requires:
+  Module::Build: 0.36
+generated_by: 'Module::Build version 0.3601'
+license: perl
+meta-spec:
+  url: http://module-build.sourceforge.net/META-spec-v1.4.html
+  version: 1.4
+name: Time-y2038
 provides:
   Time::y2038:
     file: lib/Time/y2038.pm
-    version: 20081111
+    version: 20100218
   Time::y2038::Everywhere:
     file: lib/Time/y2038/Everywhere.pm
-    version: 20081111
-generated_by: Module::Build version 0.3
-meta-spec:
-  url: http://module-build.sourceforge.net/META-spec-v1.2.html
-  version: 1.2
+    version: 20100218
+requires:
+  perl: v5.6.1
+resources:
+  license: http://dev.perl.org/licenses/
+version: 20100218
@@ -1,42 +0,0 @@
-This file contains message digests of all files listed in MANIFEST,
-signed via the Module::Signature module, version 0.55.
-
-To verify the content in this distribution, first make sure you have
-Module::Signature installed, then type:
-
-    % cpansign -v
-
-It will check each file's integrity, as well as the signature's
-validity.  If "==> Signature verified OK! <==" is not displayed,
-the distribution may already have been compromised, and you should
-not run its Makefile.PL or Build.PL.
-
------BEGIN PGP SIGNED MESSAGE-----
-Hash: SHA1
-
-SHA1 86f971cde11d55c132b442933c01634cab54586b Build.PL
-SHA1 255de6249cf6df584cb3d0efb8681407e12e844b Changes
-SHA1 fb110d0098fa623a2d6566e86a6d630110f60d23 INSTALL
-SHA1 02f5e7b8483d8f395dc26aac98d417a6596263b7 MANIFEST
-SHA1 9698a8c937c13d598301a07120b1472d163390f6 MANIFEST.SKIP
-SHA1 9b7b1358f90cf465521caa5b7d3e0cd603024362 META.yml
-SHA1 394150edea65b31e2054df4bac62eb301fa90507 check_max.c
-SHA1 54ec76512053479e4c6e96e7880d3d22a99ddee8 inc/Local/Module/Build.pm
-SHA1 80426fe1294d7ca50471b6fa931a102030dd6ae4 lib/Time/y2038.pm
-SHA1 666e25e498a3bd6bdb9bcab3f9e5b8812e4567f3 lib/Time/y2038.xs
-SHA1 14090ec73b8f16dd06c58b0282f049b0a66c60ea lib/Time/y2038/Everywhere.pm
-SHA1 86c1c0b6f58666b6ac2247312cf459b903b75dd5 munge_config
-SHA1 2997ee7e8bed8f51793ff239c094bd1838d12e06 t/everywhere.t
-SHA1 a9dd5089dd4bfae40c9cfa72d2bcb7be7f46591a t/time.t
-SHA1 0f7a96de5a04d9326e963cb480a50ebf783c5b2c t/timegm.t
-SHA1 a915486e0da8713dead77ca21d041c8ae6b6676e typemap
-SHA1 96834fb722bb467065888454275a21c36fb7c1b3 y2038/time64.c
-SHA1 c0dfb7f4e4dca8eaa3a28bbe0b680ebd36eaf722 y2038/time64.h
-SHA1 7761df7832a8e3df7976d9a278d684ea37b2bdc1 y2038/time64_config.h.in
------BEGIN PGP SIGNATURE-----
-Version: GnuPG v1.4.9 (Darwin)
-
-iEYEARECAAYFAkkaF60ACgkQWMohlhD1QycmZwCgk/u5RwRMO9GqzMqaRNQjoPEJ
-tRwAoMZsXozLnb9rKEfsc0xNH5H9Mlny
-=pdkF
------END PGP SIGNATURE-----
@@ -1,8 +1,11 @@
 /* A little program to test the limits of your system's time functions */
 
+#include "time64_config.h"
 #include <time.h>
 #include <stdio.h>
 #include <math.h>
+#include <stdlib.h>
+#include <string.h>
 
 struct tm Test_TM;
 
@@ -12,13 +15,25 @@ time_t Time_Min;
 time_t Time_Zero = 0;
 
 
+char *dump_date(const struct tm *date) {
+    char *dump = malloc(80 * sizeof(char));
+    sprintf(
+        dump,
+        "{ %d, %d, %d, %d, %d, %d }",
+        date->tm_sec, date->tm_min, date->tm_hour, date->tm_mday, date->tm_mon, date->tm_year
+    );
+
+    return dump;
+}
+
+
 /* Visual C++ 2008's difftime() can't do negative times */
 double my_difftime(time_t left, time_t right) {
-	double diff = (double)left - (double)right;
-	return diff;
+    double diff = (double)left - (double)right;
+    return diff;
 }
 
-void check_date_max( struct tm * (*date_func)(const time_t *), char *func_name ) {
+time_t check_date_max( struct tm * (*date_func)(const time_t *), const char *func_name ) {
     struct tm *date;
     time_t time        = Time_Max;
     time_t good_time   = 0;
@@ -43,11 +58,11 @@ void check_date_max( struct tm * (*date_func)(const time_t *), char *func_name )
         }
     } while(time_change > 0 && good_time < Time_Max);
 
-    printf("%s_max %.0f\n", func_name, my_difftime(good_time, Time_Zero));
+    return(good_time);
 }
 
 
-void check_date_min( struct tm * (*date_func)(const time_t *), char *func_name ) {
+time_t check_date_min( struct tm * (*date_func)(const time_t *), const char *func_name ) {
     struct tm *date;
     time_t time        = Time_Min;
     time_t good_time   = 0;
@@ -60,7 +75,7 @@ void check_date_min( struct tm * (*date_func)(const time_t *), char *func_name )
 
         time_change /= 2;
 
-        /* gmtime() broke or tm_year overflowed or time_t overflowed */
+        /* date_func() broke or tm_year overflowed or time_t overflowed */
         if(date == NULL || date->tm_year > 70 || time > good_time) {
             printf(" failed\n");
             time -= time_change;
@@ -70,9 +85,79 @@ void check_date_min( struct tm * (*date_func)(const time_t *), char *func_name )
             good_time = time;
             time += time_change;
         }
-    } while((time_change > 0) && (good_time > Time_Min));
+    } while((time_change < 0) && (good_time > Time_Min));
+
+    return(good_time);
+}
+
+
+struct tm * check_to_time_max( time_t (*to_time)(struct tm *), const char *func_name,
+                          struct tm * (*to_date)(const time_t *) )
+{
+    time_t round_trip;
+    time_t time        = Time_Max;
+    time_t good_time   = 0;
+    struct tm *date;
+    struct tm *good_date = malloc(sizeof(struct tm));
+    time_t time_change = Time_Max;
+
+    /* Binary search for the exact failure point */
+    do {
+        printf("# Trying %s(%.0f) max...", func_name, my_difftime(time, Time_Zero));
+        date = (*to_date)(&time);
+        round_trip = (*to_time)(date);
+
+        time_change /= 2;
+
+        /* date_func() broke or tm_year overflowed or time_t overflowed */
+        if(time != round_trip) {
+            printf(" failed\n");
+            time -= time_change;
+        }
+        else {
+            printf(" success\n");
+            good_time = time;
+            memcpy(good_date, date, sizeof(struct tm));
+            time += time_change;
+        }
+    } while(time_change > 0 && good_time < Time_Max);
+
+    return(good_date);
+}
+
+
+struct tm * check_to_time_min( time_t (*to_time)(struct tm *), const char *func_name,
+                          struct tm * (*to_date)(const time_t *) )
+{
+    time_t round_trip;
+    time_t time        = Time_Min;
+    time_t good_time   = 0;
+    struct tm *date;
+    struct tm *good_date = malloc(sizeof(struct tm));
+    time_t time_change = Time_Min;
+
+    /* Binary search for the exact failure point */
+    do {
+        printf("# Trying %s(%.0f) min...", func_name, my_difftime(time, Time_Zero));
+        date = (*to_date)(&time);
+        round_trip = (*to_time)(date);
+
+        time_change /= 2;
+
+        /* date_func() broke or tm_year overflowed or time_t overflowed */
+        if(time != round_trip) {
+            printf(" failed\n");
+            time -= time_change;
+        }
+        else {
+            printf(" success\n");
+            good_time = time;
+            memcpy(good_date, date, sizeof(struct tm));
+            time += time_change;
+        }
+    } while((time_change < 0) && (good_time > Time_Min));
 
-    printf("%s_min %.0f\n", func_name, my_difftime(good_time, Time_Zero));
+    return(good_date);
 }
 
 
@@ -80,7 +165,8 @@ void guess_time_limits_from_types(void) {
     if( sizeof(time_t) == 4 ) {
         /* y2038 bug, out to 2**31-1 */
         Time_Max =  2147483647;
-        Time_Min = -2147483648;
+        Time_Min = -2147483647 - 1;     /* "C90 doesn't have negative constants, only
+                                           positive ones that have been negated." */
     }
     else if( sizeof(time_t) >= 8 ) {
         /* The compiler might warn about overflowing in the assignments
@@ -101,12 +187,95 @@ void guess_time_limits_from_types(void) {
     }
 }
 
+
+/* Dump a tm struct as a json fragment */
+char * tm_as_json(const struct tm* date) {
+    char *date_json = malloc(sizeof(char) * 512);
+#ifdef HAS_TM_TM_ZONE
+    char zone_json[32];
+#endif
+#ifdef HAS_TM_TM_GMTOFF
+    char gmtoff_json[32];
+#endif
+
+    sprintf(date_json,
+            "\"tm_sec\": %d, \"tm_min\": %d, \"tm_hour\": %d, \"tm_mday\": %d, \"tm_mon\": %d, \"tm_year\": %d, \"tm_wday\": %d, \"tm_yday\": %d, \"tm_isdst\": %d",
+            date->tm_sec, date->tm_min, date->tm_hour, date->tm_mday,
+            date->tm_mon, date->tm_year, date->tm_wday, date->tm_yday, date->tm_isdst
+    );
+
+#ifdef HAS_TM_TM_ZONE
+    sprintf(zone_json, ", \"tm_zone\": \"%s\"", date->tm_zone);
+    strcat(date_json, zone_json);
+#endif
+#ifdef HAS_TM_TM_GMTOFF
+    sprintf(gmtoff_json, ", \"tm_gmtoff\": %ld", date->tm_gmtoff);
+    strcat(date_json, gmtoff_json);
+#endif
+
+    return date_json;
+}
+
+
 int main(void) {
+    time_t gmtime_max;
+    time_t gmtime_min;
+    time_t localtime_max;
+    time_t localtime_min;
+#ifdef HAS_TIMEGM
+    struct tm* timegm_max;
+    struct tm* timegm_min;
+#endif
+    struct tm* mktime_max;
+    struct tm* mktime_min;
+
     guess_time_limits_from_types();
-    check_date_max(gmtime, "gmtime");
-    check_date_max(localtime, "localtime");
-    check_date_min(gmtime, "gmtime");
-    check_date_min(localtime, "localtime");
+
+    gmtime_max = check_date_max(gmtime, "gmtime");
+    gmtime_min = check_date_min(gmtime, "gmtime");
+
+    localtime_max = check_date_max(localtime, "localtime");
+    localtime_min = check_date_min(localtime, "localtime");
+
+#ifdef HAS_TIMEGM
+    Time_Max = gmtime_max;
+    Time_Min = gmtime_min;
+    timegm_max = check_to_time_max(timegm, "timegm", gmtime);
+    timegm_min = check_to_time_min(timegm, "timegm", gmtime);
+#endif
+
+    Time_Max = localtime_max;
+    Time_Min = localtime_min;
+    mktime_max = check_to_time_max(mktime, "mktime", localtime);
+    mktime_min = check_to_time_min(mktime, "mktime", localtime);
+
+    printf("# system time.h limits, as JSON\n");
+    printf("{\n");
+
+    printf("    \"gmtime\": { \"max\": %.0f, \"min\": %0.f },\n",
+           my_difftime(gmtime_max, Time_Zero),
+           my_difftime(gmtime_min, Time_Zero)
+    );
+
+    printf("    \"localtime\": { \"max\": %.0f, \"min\": %0.f },\n",
+           my_difftime(localtime_max, Time_Zero),
+           my_difftime(localtime_min, Time_Zero)
+    );
+
+    printf("    \"mktime\": {\n");
+    printf("        \"max\": { %s },\n", tm_as_json(mktime_max));
+    printf("        \"min\": { %s }\n", tm_as_json(mktime_min));
+    printf("    }\n");
+
+#ifdef HAS_TIMEGM
+    printf("    ,\n");
+    printf("    \"timegm\": {\n");
+    printf("        \"max\": { %s },\n", tm_as_json(timegm_max));
+    printf("        \"min\": { %s }\n", tm_as_json(timegm_min));
+    printf("    }\n");
+#endif
+
+    printf("}\n");
 
     return 0;
 }
@@ -3,57 +3,201 @@ package Local::Module::Build;
 use strict;
 use base qw(Module::Build);
 
+use ExtUtils::CBuilder;
+use JSON;
+
+sub is_osx_106 {
+    return 0 unless $^O eq 'darwin';
+    my $version = `sw_vers -productVersion`;
+    return $version =~ m{^10\.6\.};
+}
+
+sub probe_system_time {
+    my $self = shift;
+    $self->note_time_capabilities;
+    $self->munge("y2038/time64_config.h.in" => "y2038/time64_config.h");
+    $self->note_time_limits;
+    $self->munge("y2038/time64_limits.h.in" => "y2038/time64_limits.h");
+}
+
+sub munge {
+    my $self = shift;
+    my($src, $dest) = @_;
+
+    return if $self->up_to_date(["munge_config",$src] => [$dest]);
+    system $^X, "munge_config", $src, $dest;
+    $self->add_to_cleanup($dest);
+}
+
+sub note_time_capabilities {
+    my $self = shift;
+
+    return if $self->up_to_date(["Build", "y2038/time64_limits.h.in"], ["y2038/time64_limits.h"]);
+    my %tests = (
+        HAS_TIMEGM      => <<'END',
+    struct tm *date;
+    time_t zero;
+
+    date = localtime(&zero);
+    zero = timegm(date);
+END
+
+        HAS_GMTIME_R    => <<'END',
+    struct tm date;
+    time_t zero = 0;
+    (void)gmtime_r(&zero, &date);
+END
+
+        HAS_LOCALTIME_R => <<'END',
+    struct tm date;
+    time_t zero = 0;
+    (void)localtime_r(&zero, &date);
+END
+
+        HAS_TM_TM_GMTOFF => <<'END',
+    struct tm *date;
+    time_t zero;
+    int offset;
+
+    date = gmtime(&zero);
+    offset = date->tm_gmtoff;
+END
+
+        HAS_TM_TM_ZONE => <<'END',
+    struct tm *date;
+    time_t zero;
+    char *zone;
+
+    date = gmtime(&zero);
+    zone = date->tm_zone;
+END
+    );
+
+    warn "Probing time.h capabilities.\n";
+    warn "You may see some C errors, that's ok.\n";
+    my $cb = ExtUtils::CBuilder->new( quiet => 1 );
+    for my $test (keys %tests) {
+        my $code = $tests{$test};
+
+        $code = <<END;
+#include <time.h>
+
+int main() {
+$code
+
+    return 0;
+}
+END
+
+        open my $fh, ">", "try.c" or die "Can't write try.c: $!";
+        print $fh $code;
+        close $fh;
+
+        my $exe = eval {
+            # Compile AND link to force undefined symbols to error
+            my $obj = $cb->compile(source => "try.c");
+            my $exe = $cb->link_executable(objects => $obj, exe_file => "try");
+            unlink $obj;
+            $exe;
+        };
+        $self->notes($test, $exe ? 1 : 0);
+        unlink $exe if $exe;
+        unlink "try.c";
+    }
+}
+
+
 sub note_time_limits {
     my $self = shift;
 
     my $source = "check_max.c";
-    my $exe    = "check_max";
+    my $exe    = $self->notes("check_max") || "check_max";
     unless( $self->up_to_date($source => $exe) ) {
         warn "Building a program to test the range of your system time functions...\n";
         my $cb = $self->cbuilder;
         my $obj = $cb->compile(source => "check_max.c", include_dirs => ['y2038']);
         $exe = $cb->link_executable(objects => $obj, exe_file => $exe);
+        $exe = $self->find_real_exe($exe);
+        $self->notes(check_max => $exe);
+        $self->add_to_cleanup($obj, $exe);
     }
 
-    $self->add_to_cleanup($exe);
-
-    return if $self->up_to_date(["munge_config", $exe] => "y2038/time64_config.h");
+    return if $self->up_to_date(["y2038/time64_limits.h.in", "munge_config", $exe]
+                                => "y2038/time64_limits.h");
     warn "  and running it...\n";
 
-    my @maxes = `./$exe`;
+    my $json = `./$exe`;
+    $json =~ s{^\#.*\n}{}gm;
+    my $limits = from_json($json);
 
     warn "  Done.\n";
 
-    my %limits;
-    for my $line (@maxes) {
-        next if $line =~ /^#/;
-        my($key, $val) = split /\s+/, $line;
-        next unless $key and $val;
-        $limits{$key} = $val;
+    my %config;
+    for my $key (qw(gmtime localtime)) {
+        for my $limit (qw(min max)) {
+            $config{$key."_".$limit} = $limits->{$key}{$limit};
+        }
+    }
+
+    for my $key (qw(mktime timegm)) {
+        for my $limit (qw(min max)) {
+            my $struct = $limits->{$key}{$limit};
+            for my $tm (keys %$struct) {
+                $config{$key."_".$limit."_".$tm} = $struct->{$tm};
+            }
+        }
     }
 
     # Windows lies about being able to handle just a little bit of
     # negative time.
     for my $key (qw(gmtime_min localtime_min)) {
-        if( -10_000 < $limits{$key} && $limits{$key} < 0 ) {
-            $limits{$key} = 0;
+        if( -10_000 < $config{$key} && $config{$key} < 0 ) {
+            $config{$key} = 0;
         }
     }
 
-    for my $key (sort { $a cmp $b } keys %limits) {
-        my $val = $limits{$key};
-        warn sprintf "%15s:  %d\n", $key, $val;
-        $self->notes($key, $limits{$key});
+    # OS X 10.6's gmtime is broken before -70546986201600
+    # See Apple bug 7654647
+    if( is_osx_106 ) {
+        for my $key (qw(gmtime_min localtime_min)) {
+            if( $config{$key} < -70546986201600 ) {
+                $config{$key} = -70546986201600;
+            }
+        }
+    }
+
+    for my $key (sort { $a cmp $b } keys %config) {
+        my $val = $config{$key};
+        warn sprintf "%15s:  %s\n", $key, $val;
+        $self->notes($key, "$val");
     }
 
     return;
 }
 
 
+# This is necessary because Cygwin nerotically puts a .exe on
+# every executable.  This appears to be built into gcc.
+sub find_real_exe {
+    my $self = shift;
+    my $exe = shift;
+
+    my $real_exe;
+    for ($exe, "$exe.exe") {
+        $real_exe = $_ if -e;
+    }
+
+    warn "Can't find the executable, thought it was $exe"
+        unless defined $real_exe;
+
+    return $real_exe;
+}
+
+
 sub ACTION_code {
     my $self = shift;
 
-    $self->note_time_limits;
+    $self->probe_system_time;
 
     return $self->SUPER::ACTION_code(@_);
 }
@@ -3,7 +3,7 @@ package Time::y2038::Everywhere;
 use strict;
 use warnings;
 
-our $VERSION = 20081111;
+our $VERSION = '20100218';
 
 use Time::y2038;
 
@@ -6,7 +6,7 @@ use warnings;
 use base qw(Exporter);
 use XSLoader;
 
-our $VERSION = 20081111;
+our $VERSION = '20100218';
 our @EXPORT = qw(localtime gmtime timegm timelocal);
 
 XSLoader::load __PACKAGE__, $VERSION;
@@ -7,8 +7,9 @@ my $build = Module::Build->current;
 
 my %config = (%Config, $build->notes);
 
+my $input_file  = shift;
 my $output_file = shift;
-my $input_file  = $output_file . ".in";
+
 
 open my $input_fh, $input_file          or die "Can't open $input_file: $!";
 open my $output_fh, ">$output_file"     or die "Can't open $output_file: $!";
@@ -22,12 +23,7 @@ print $output_fh <<"END";
 END
 
 while(<$input_fh>) {
-    my $matched = s{%%(.*)%%}{$config{$1}}g;
-
-    if( $matched and !defined $config{$1} and !length $config{$1} ) {
-        warn "Your configuration is missing \$config{$1}.\n".
-             "Please edit $output_file by hand to fix.\n"
-    }
+    my $matched = s{%%(.*)%%}{defined $config{$1} ? $config{$1} : ''}eg;
 
     print $output_fh $_;
 }
@@ -12,5 +12,3 @@ use Test::More 'no_plan';
 
 is   gmtime(2**52),    "Sat Dec  6 03:48:16 142715360";
 like localtime(2**52), qr/Dec .* 142715360/;
-
-
@@ -14,6 +14,32 @@ BEGIN {
 local $ENV{TZ} = 'US/Pacific';
 my $Test_Localtime = localtime(0) eq 'Wed Dec 31 16:00:00 1969';
 
+# Compare local time +/2 hours.  Ignore seconds, minutes and dst
+my $Epsilon = [undef, undef, 2, 0, 0, 0, 0, 0, undef];
+
+sub date_ok {
+    my($have_date, $want_date, $epsilon, $name) = @_;
+
+    my $nok = 0;
+    for my $idx (0..$#{$want_date}) {
+        my $have = $have_date->[$idx];
+        my $want = $want_date->[$idx];
+        my $ep   = $epsilon->[$idx];
+        next unless defined $ep;
+
+        $nok ||= abs($have - $want) > $epsilon;
+    }
+
+    ok( !$nok, $name );
+    if( $nok ) {
+        diag sprintf <<END, explain $have_date, explain $want_date;
+have: %s
+want: %s
+END
+    }
+
+    return $nok;
+}
 
 # Test that we match the core's results inside the safe range.
 {
@@ -42,13 +68,56 @@ my $Test_Localtime = localtime(0) eq 'Wed Dec 31 16:00:00 1969';
 SKIP: {
     skip "localtime() tests specific to US/Pacific time zone", 6 unless $Test_Localtime;
 
-    is_deeply( [localtime(2**52)],  [16, 48, 19, 5, 11, 142713460, 5, 339, 0], 'localtime(2**52)' );
-    is_deeply( [localtime(-2**52)], [44, 11, 12, 25, 0, -142713321, 1, 24, 0], 'localtime(-2**52)' );
+    date_ok( [localtime(2**52)],  [16, 48, 19, 5, 11, 142713460, 5, 339, 0],
+             $Epsilon, 'localtime(2**52)'
+    );
+    date_ok( [localtime(-2**52)], [44, 11, 12, 25, 0, -142713321, 1, 24, 0],
+             $Epsilon, 'localtime(-2**52)'
+    );
     is_deeply( [localtime(1224479316)], [36, 8, 22, 19, 9, 108, 0, 292, 1], 'localtime() w/dst' );
 
-    is( localtime(2**52),      'Fri Dec  5 19:48:16 142715360' );
-    is( localtime(-2**52),     'Mon Jan 25 12:11:44 -142711421' );
-    is( localtime(1224479316), 'Sun Oct 19 22:08:36 2008' );
+    # This is inverted because hash keys get stringified and the
+    # big numbers may lose accuracy.
+    my %times = (
+        'Fri Dec  5 .* 142715360'         => 2**52,
+        'Mon Jun 19 .* 71358665'          => 2**51,
+        'Tue Sep 25 .* 35680317'          => 2**50,
+        'Mon Oct 25 .* 3058'              => 2**35,
+        'Fri Mar  7 .* 881'               => -2**35,
+        'Thu Apr  7 .* -35676378'         => -2**50,
+        'Sat Jul 14 .* -71354726'         => -2**51,
+        'Mon Jan 25 .* -142711421'        => -2**52,
+        'Sun Oct 19 22:08:36 2008'        => 1224479316,
+    );
+    for my $want (keys %times) {
+        my $time = $times{$want};
+        like localtime($time), qr/$want/, sprintf "localtime(%.0f)", $time;
+    }
+}
+
+
+# Some sanity tests for the far, far future and far, far past
+{
+    my %time2year = (
+        -2**62  => -146138510344,
+        -2**52  => -142711421,
+        -2**48  => -8917617,
+        -2**46  => -2227927,
+         2**46  => 2231866,
+         2**48  => 8921556,
+         2**52  => 142715360,
+         2**62  => 146138514283
+    );
+
+    for my $time (sort keys %time2year) {
+        my $want = $time2year{$time};
+
+        my $have = (gmtime($time))[5] + 1900;
+        is $have, $want, "year check, gmtime($time)";
+
+        $have = (localtime($time))[5] + 1900;
+        is $have, $want, "year check, localtime($time)";
+    }
 }
 
 
@@ -59,12 +128,12 @@ for my $name (qw(gmtime localtime)) {
     };
 
     # Test in void context
-#line 18
+#line 132
     warning_like {
         1;
         $func->(0);
         1;
-    } qr/^\QUseless use of $name() in void context at $0 line 20.\E$/,
+    } qr/^\QUseless use of $name() in void context at $0 line 134.\E$/,
       "void context warning";
 
 
@@ -79,13 +148,13 @@ for my $name (qw(gmtime localtime)) {
 
     # Test too big or small a time.
     my $huge_time = sprintf "%.0f", 2**65;
-#line 58
+#line 152
     warning_like {
         is $func->($huge_time), undef;
-    } qr/^\Q$name($huge_time) can not be represented at $0 line 59\E/;
+    } qr/^\Q$name($huge_time) can not be represented at $0 line 153\E/;
 
-#line 63
+#line 157
     warning_like {
         is $func->(-$huge_time), undef;
-    } qr/^\Q$name(-$huge_time) can not be represented at $0 line 64\E/;
+    } qr/^\Q$name(-$huge_time) can not be represented at $0 line 158\E/;
 }
@@ -46,12 +46,103 @@ gmtime64_r() is a 64-bit equivalent of gmtime_r().
 #include <time.h>
 #include <errno.h>
 #include "time64.h"
+#include "time64_limits.h"
+
+struct tm SYSTEM_MKTIME_MAX = {
+SYSTEM_MKTIME_MAX_TM_SEC,
+SYSTEM_MKTIME_MAX_TM_MIN, 
+SYSTEM_MKTIME_MAX_TM_HOUR, 
+SYSTEM_MKTIME_MAX_TM_MDAY,
+SYSTEM_MKTIME_MAX_TM_MON,
+SYSTEM_MKTIME_MAX_TM_YEAR,
+SYSTEM_MKTIME_MAX_TM_WDAY,
+SYSTEM_MKTIME_MAX_TM_YDAY,
+SYSTEM_MKTIME_MAX_TM_ISDST
+#ifdef HAS_TM_TM_GMTOFF
+,SYSTEM_MKTIME_MAX_TM_GMTOFF
+#else
+,0
+#endif
+#ifdef HAS_TM_TM_ZONE
+,SYSTEM_MKTIME_MAX_TM_ZONE
+#else
+,""
+#endif
+};
+
+const struct tm SYSTEM_MKTIME_MIN = {
+SYSTEM_MKTIME_MIN_TM_SEC,
+SYSTEM_MKTIME_MIN_TM_MIN, 
+SYSTEM_MKTIME_MIN_TM_HOUR, 
+SYSTEM_MKTIME_MIN_TM_MDAY,
+SYSTEM_MKTIME_MIN_TM_MON,
+SYSTEM_MKTIME_MIN_TM_YEAR,
+SYSTEM_MKTIME_MIN_TM_WDAY,
+SYSTEM_MKTIME_MIN_TM_YDAY,
+SYSTEM_MKTIME_MIN_TM_ISDST
+#ifdef HAS_TM_TM_GMTOFF
+,SYSTEM_MKTIME_MIN_TM_GMTOFF
+#else
+,0
+#endif
+#ifdef HAS_TM_TM_ZONE
+,SYSTEM_MKTIME_MIN_TM_ZONE
+#else
+,""
+#endif
+};
+
+#ifdef HAS_TIMEGM
+const struct tm SYSTEM_TIMEGM_MAX = {
+SYSTEM_TIMEGM_MAX_TM_SEC,
+SYSTEM_TIMEGM_MAX_TM_MIN, 
+SYSTEM_TIMEGM_MAX_TM_HOUR, 
+SYSTEM_TIMEGM_MAX_TM_MDAY,
+SYSTEM_TIMEGM_MAX_TM_MON,
+SYSTEM_TIMEGM_MAX_TM_YEAR,
+SYSTEM_TIMEGM_MAX_TM_WDAY,
+SYSTEM_TIMEGM_MAX_TM_YDAY,
+SYSTEM_TIMEGM_MAX_TM_ISDST
+#ifdef HAS_TM_TM_GMTOFF
+,SYSTEM_TIMEGM_MAX_TM_GMTOFF
+#else
+,0
+#endif
+#ifdef HAS_TM_TM_ZONE
+,SYSTEM_TIMEGM_MAX_TM_ZONE
+#else
+,""
+#endif
+};
+
+const struct tm SYSTEM_TIMEGM_MIN = {
+SYSTEM_TIMEGM_MIN_TM_SEC,
+SYSTEM_TIMEGM_MIN_TM_MIN, 
+SYSTEM_TIMEGM_MIN_TM_HOUR, 
+SYSTEM_TIMEGM_MIN_TM_MDAY,
+SYSTEM_TIMEGM_MIN_TM_MON,
+SYSTEM_TIMEGM_MIN_TM_YEAR,
+SYSTEM_TIMEGM_MIN_TM_WDAY,
+SYSTEM_TIMEGM_MIN_TM_YDAY,
+SYSTEM_TIMEGM_MIN_TM_ISDST
+#ifdef HAS_TM_TM_GMTOFF
+,SYSTEM_TIMEGM_MIN_TM_GMTOFF
+#else
+,0
+#endif
+#ifdef HAS_TM_TM_ZONE
+,SYSTEM_TIMEGM_MIN_TM_ZONE
+#else
+,""
+#endif
+};
+#endif /* HAS_TIMEGM */
 
 
 /* Spec says except for stftime() and the _r() functions, these
    all return static memory.  Stabbings! */
 static struct TM   Static_Return_Date;
-/* static char        Static_Return_String[1024]; */
+static char        Static_Return_String[35];
 
 static const int days_in_month[2][12] = {
     {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
@@ -63,6 +154,15 @@ static const int julian_days_by_month[2][12] = {
     {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335},
 };
 
+static char wday_name[7][3] = {
+    "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+static char mon_name[12][3] = {
+    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
 static const int length_of_year[2] = { 365, 366 };
 
 /* Some numbers relating to the gregorian cycle */
@@ -118,8 +218,8 @@ static const int dow_year_start[SOLAR_CYCLE_LENGTH] = {
 #define CHEAT_DAYS  (1199145600 / 24 / 60 / 60)
 #define CHEAT_YEARS 108
 
-#define IS_LEAP(n)	((!(((n) + 1900) % 400) || (!(((n) + 1900) % 4) && (((n) + 1900) % 100))) != 0)
-#define WRAP(a,b,m)	((a) = ((a) <  0  ) ? ((b)--, (a) + (m)) : (a))
+#define IS_LEAP(n)      ((!(((n) + 1900) % 400) || (!(((n) + 1900) % 4) && (((n) + 1900) % 100))) != 0)
+#define WRAP(a,b,m)     ((a) = ((a) <  0  ) ? ((b)--, (a) + (m)) : (a))
 
 #ifdef USE_SYSTEM_LOCALTIME
 #    define SHOULD_USE_SYSTEM_LOCALTIME(a)  (       \
@@ -141,27 +241,80 @@ static const int dow_year_start[SOLAR_CYCLE_LENGTH] = {
 
 /* Multi varadic macros are a C99 thing, alas */
 #ifdef TIME_64_DEBUG
-#    define TRACE(format) (fprintf(stderr, format))
-#    define TRACE1(format, var1)    (fprintf(stderr, format, var1))
-#    define TRACE2(format, var1, var2)    (fprintf(stderr, format, var1, var2))
-#    define TRACE3(format, var1, var2, var3)    (fprintf(stderr, format, var1, var2, var3))
+#    define TIME64_TRACE(format) (fprintf(stderr, format))
+#    define TIME64_TRACE1(format, var1)    (fprintf(stderr, format, var1))
+#    define TIME64_TRACE2(format, var1, var2)    (fprintf(stderr, format, var1, var2))
+#    define TIME64_TRACE3(format, var1, var2, var3)    (fprintf(stderr, format, var1, var2, var3))
 #else
-#    define TRACE(format) ((void)0)
-#    define TRACE1(format, var1) ((void)0)
-#    define TRACE2(format, var1, var2) ((void)0)
-#    define TRACE3(format, var1, var2, var3) ((void)0)
+#    define TIME64_TRACE(format) ((void)0)
+#    define TIME64_TRACE1(format, var1) ((void)0)
+#    define TIME64_TRACE2(format, var1, var2) ((void)0)
+#    define TIME64_TRACE3(format, var1, var2, var3) ((void)0)
 #endif
 
 
 static int is_exception_century(Year year)
 {
     int is_exception = ((year % 100 == 0) && !(year % 400 == 0));
-    TRACE1("# is_exception_century: %s\n", is_exception ? "yes" : "no");
+    TIME64_TRACE1("# is_exception_century: %s\n", is_exception ? "yes" : "no");
 
     return(is_exception);
 }
 
 
+/* Compare two dates.
+   The result is like cmp.
+   Ignores things like gmtoffset and dst
+*/
+int cmp_date( const struct TM* left, const struct tm* right ) {
+    if( left->tm_year > right->tm_year )
+        return 1;
+    else if( left->tm_year < right->tm_year )
+        return -1;
+
+    if( left->tm_mon > right->tm_mon )
+        return 1;
+    else if( left->tm_mon < right->tm_mon )
+        return -1;
+
+    if( left->tm_mday > right->tm_mday )
+        return 1;
+    else if( left->tm_mday < right->tm_mday )
+        return -1;
+
+    if( left->tm_hour > right->tm_hour )
+        return 1;
+    else if( left->tm_hour < right->tm_hour )
+        return -1;
+
+    if( left->tm_min > right->tm_min )
+        return 1;
+    else if( left->tm_min < right->tm_min )
+        return -1;
+
+    if( left->tm_sec > right->tm_sec )
+        return 1;
+    else if( left->tm_sec < right->tm_sec )
+        return -1;
+
+    return 0;
+}
+
+
+/* Check if a date is safely inside a range.
+   The intention is to check if its a few days inside.
+*/
+int date_in_safe_range( const struct TM* date, const struct tm* min, const struct tm* max ) {
+    if( cmp_date(date, min) == -1 )
+        return 0;
+
+    if( cmp_date(date, max) == 1 )
+        return 0;
+
+    return 1;
+}
+
+
 /* timegm() is not in the C or POSIX spec, but it is such a useful
    extension I would be remiss in leaving it out.  Also I need it
    for localtime64()
@@ -183,7 +336,7 @@ Time64_T timegm64(const struct TM *date) {
         orig_year -= cycles * 400;
         days      += (Time64_T)cycles * days_in_gregorian_cycle;
     }
-    TRACE3("# timegm/ cycles: %d, days: %lld, orig_year: %lld\n", cycles, days, orig_year);
+    TIME64_TRACE3("# timegm/ cycles: %d, days: %lld, orig_year: %lld\n", cycles, days, orig_year);
 
     if( orig_year > 70 ) {
         year = 70;
@@ -261,7 +414,7 @@ static Year cycle_offset(Year year)
     exceptions  = year_diff / 100;
     exceptions -= year_diff / 400;
 
-    TRACE3("# year: %lld, exceptions: %lld, year_diff: %lld\n",
+    TIME64_TRACE3("# year: %lld, exceptions: %lld, year_diff: %lld\n",
           year, exceptions, year_diff);
 
     return exceptions * 16;
@@ -320,7 +473,7 @@ static int safe_year(const Year year)
     else
         assert(0);
 
-    TRACE3("# year: %lld, year_cycle: %lld, safe_year: %d\n",
+    TIME64_TRACE3("# year: %lld, year_cycle: %lld, safe_year: %d\n",
           year, year_cycle, safe_year);
 
     assert(safe_year <= MAX_SAFE_YEAR && safe_year >= MIN_SAFE_YEAR);
@@ -329,7 +482,7 @@ static int safe_year(const Year year)
 }
 
 
-void copy_tm_to_TM(const struct tm *src, struct TM *dest) {
+void copy_tm_to_TM64(const struct tm *src, struct TM *dest) {
     if( src == NULL ) {
         memset(dest, 0, sizeof(*dest));
     }
@@ -361,7 +514,7 @@ void copy_tm_to_TM(const struct tm *src, struct TM *dest) {
 }
 
 
-void copy_TM_to_tm(const struct TM *src, struct tm *dest) {
+void copy_TM64_to_tm(const struct TM *src, struct tm *dest) {
     if( src == NULL ) {
         memset(dest, 0, sizeof(*dest));
     }
@@ -394,8 +547,8 @@ void copy_TM_to_tm(const struct TM *src, struct tm *dest) {
 
 
 /* Simulate localtime_r() to the best of our ability */
-struct tm * fake_localtime_r(const time_t *clock, struct tm *result) {
-    const struct tm *static_result = localtime(clock);
+struct tm * fake_localtime_r(const time_t *time, struct tm *result) {
+    const struct tm *static_result = localtime(time);
 
     assert(result != NULL);
 
@@ -411,8 +564,8 @@ struct tm * fake_localtime_r(const time_t *clock, struct tm *result) {
 
 
 /* Simulate gmtime_r() to the best of our ability */
-struct tm * fake_gmtime_r(const time_t *clock, struct tm *result) {
-    const struct tm *static_result = gmtime(clock);
+struct tm * fake_gmtime_r(const time_t *time, struct tm *result) {
+    const struct tm *static_result = gmtime(time);
 
     assert(result != NULL);
 
@@ -458,15 +611,17 @@ Time64_T mktime64(const struct TM *input_date) {
     Time64_T  time;
     Year      year = input_date->tm_year + 1900;
 
-    if( MIN_SAFE_YEAR <= year && year <= MAX_SAFE_YEAR ) {
-        copy_TM_to_tm(input_date, &safe_date);
+    if( date_in_safe_range(input_date, &SYSTEM_MKTIME_MIN, &SYSTEM_MKTIME_MAX) )
+    {
+        printf("Using system mktime\n");
+        copy_TM64_to_tm(input_date, &safe_date);
         return (Time64_T)mktime(&safe_date);
     }
 
     /* Have to make the year safe in date else it won't fit in safe_date */
     date = *input_date;
     date.tm_year = safe_year(year) - 1900;
-    copy_TM_to_tm(&date, &safe_date);
+    copy_TM64_to_tm(&date, &safe_date);
 
     time = (Time64_T)mktime(&safe_date);
 
@@ -496,11 +651,11 @@ struct TM *gmtime64_r (const Time64_T *in_time, struct TM *p)
 
     /* Use the system gmtime() if time_t is small enough */
     if( SHOULD_USE_SYSTEM_GMTIME(*in_time) ) {
-        time_t safe_time = *in_time;
+        time_t safe_time = (time_t)*in_time;
         struct tm safe_date;
         GMTIME_R(&safe_time, &safe_date);
 
-        copy_tm_to_TM(&safe_date, p);
+        copy_tm_to_TM64(&safe_date, p);
         assert(check_tm(p));
 
         return p;
@@ -621,20 +776,20 @@ struct TM *localtime64_r (const Time64_T *time, struct TM *local_tm)
 
     /* Use the system localtime() if time_t is small enough */
     if( SHOULD_USE_SYSTEM_LOCALTIME(*time) ) {
-        safe_time = *time;
+        safe_time = (time_t)*time;
 
-        TRACE1("Using system localtime for %lld\n", *time);
+        TIME64_TRACE1("Using system localtime for %lld\n", *time);
 
         LOCALTIME_R(&safe_time, &safe_date);
 
-        copy_tm_to_TM(&safe_date, local_tm);
+        copy_tm_to_TM64(&safe_date, local_tm);
         assert(check_tm(local_tm));
 
         return local_tm;
     }
 
     if( gmtime64_r(time, &gm_tm) == NULL ) {
-        TRACE1("gmtime64_r returned null for %lld\n", *time);
+        TIME64_TRACE1("gmtime64_r returned null for %lld\n", *time);
         return NULL;
     }
 
@@ -644,21 +799,21 @@ struct TM *localtime64_r (const Time64_T *time, struct TM *local_tm)
         gm_tm.tm_year < (1970 - 1900)
        )
     {
-        TRACE1("Mapping tm_year %lld to safe_year\n", (Year)gm_tm.tm_year);
+        TIME64_TRACE1("Mapping tm_year %lld to safe_year\n", (Year)gm_tm.tm_year);
         gm_tm.tm_year = safe_year((Year)(gm_tm.tm_year + 1900)) - 1900;
     }
 
-    safe_time = timegm64(&gm_tm);
+    safe_time = (time_t)timegm64(&gm_tm);
     if( LOCALTIME_R(&safe_time, &safe_date) == NULL ) {
-        TRACE1("localtime_r(%d) returned NULL\n", (int)safe_time);
+        TIME64_TRACE1("localtime_r(%d) returned NULL\n", (int)safe_time);
         return NULL;
     }
 
-    copy_tm_to_TM(&safe_date, local_tm);
+    copy_tm_to_TM64(&safe_date, local_tm);
 
     local_tm->tm_year = orig_year;
     if( local_tm->tm_year != orig_year ) {
-        TRACE2("tm_year overflow: tm_year %lld, orig_year %lld\n",
+        TIME64_TRACE2("tm_year overflow: tm_year %lld, orig_year %lld\n",
               (Year)local_tm->tm_year, (Year)orig_year);
 
 #ifdef EOVERFLOW
@@ -699,11 +854,59 @@ struct TM *localtime64_r (const Time64_T *time, struct TM *local_tm)
 }
 
 
+int valid_tm_wday( const struct TM* date ) {
+    if( 0 <= date->tm_wday && date->tm_wday <= 6 )
+        return 1;
+    else
+        return 0;
+}
+
+int valid_tm_mon( const struct TM* date ) {
+    if( 0 <= date->tm_mon && date->tm_mon <= 11 )
+        return 1;
+    else
+        return 0;
+}
+
+
+char *asctime64_r( const struct TM* date, char *result ) {
+    /* I figure everything else can be displayed, even hour 25, but if
+       these are out of range we walk off the name arrays */
+    if( !valid_tm_wday(date) || !valid_tm_mon(date) )
+        return NULL;
+
+    sprintf(result, TM64_ASCTIME_FORMAT,
+        wday_name[date->tm_wday],
+        mon_name[date->tm_mon],
+        date->tm_mday, date->tm_hour,
+        date->tm_min, date->tm_sec,
+        1900 + date->tm_year);
+
+    return result;
+}
+
+
+char *ctime64_r( const Time64_T* time, char* result ) {
+    struct TM date;
+
+    localtime64_r( time, &date );
+    return asctime64_r( &date, result );
+}
+
+
+/* Non-thread safe versions of the above */
 struct TM *localtime64(const Time64_T *time) {
     return localtime64_r(time, &Static_Return_Date);
 }
 
-
 struct TM *gmtime64(const Time64_T *time) {
     return gmtime64_r(time, &Static_Return_Date);
 }
+
+char *asctime64( const struct TM* date ) {
+    return asctime64_r( date, Static_Return_String );
+}
+
+char *ctime64( const Time64_T* time ) {
+    return asctime64(localtime64(time));
+}
@@ -1,9 +1,8 @@
-#include <time.h>
-#include "time64_config.h"
-
 #ifndef TIME64_H
 #    define TIME64_H
 
+#include <time.h>
+#include "time64_config.h"
 
 /* Set our custom types */
 typedef INT_64_T        Int64;
@@ -44,10 +43,15 @@ struct TM64 {
 /* Declare public functions */
 struct TM *gmtime64_r    (const Time64_T *, struct TM *);
 struct TM *localtime64_r (const Time64_T *, struct TM *);
-
 struct TM *gmtime64      (const Time64_T *);
 struct TM *localtime64   (const Time64_T *);
 
+char *asctime64          (const struct TM *);
+char *asctime64_r        (const struct TM *, char *);
+
+char *ctime64            (const Time64_T*);
+char *ctime64_r          (const Time64_T*, char*);
+
 Time64_T   timegm64      (const struct TM *);
 Time64_T   mktime64      (const struct TM *);
 Time64_T   timelocal64   (const struct TM *);
@@ -65,4 +69,13 @@ Time64_T   timelocal64   (const struct TM *);
 #    define GMTIME_R(clock, result)    fake_gmtime_r(clock, result)
 #endif
 
+
+/* Use a different asctime format depending on how big the year is */
+#ifdef USE_TM64
+    #define TM64_ASCTIME_FORMAT "%.3s %.3s%3d %.2d:%.2d:%.2d %lld\n"
+#else
+    #define TM64_ASCTIME_FORMAT "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n"
+#endif
+
+
 #endif
@@ -28,6 +28,12 @@
 */
 #define USE_TM64
 
+#ifdef USE_TM64
+    #define TM64_ASCTIME_FORMAT "%.3s %.3s%3d %.2d:%.2d:%.2d %lld\n"
+#else
+    #define TM64_ASCTIME_FORMAT "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n"
+#endif
+
 
 /* Availability of system functions.
 
@@ -40,7 +46,15 @@
    HAS_TIMEGM
    Define if your system has timegm(), a GNU extension.
 */
-/* Handled by config */
+#if %%HAS_GMTIME_R%%
+#define HAS_GMTIME_R
+#endif
+#if %%HAS_LOCALTIME_R%%
+#define HAS_LOCALTIME_R
+#endif
+#if %%HAS_TIMEGM%%
+#define HAS_TIMEGM
+#endif
 
 
 /* Details of non-standard tm struct elements.
@@ -53,31 +67,25 @@
    True if your tm struct has a "tm_zone" element.
    A BSD extension.
 */
-/* #define HAS_TM_TM_GMTOFF */
-/* #define HAS_TM_TM_ZONE   */
+#if %%HAS_TM_TM_GMTOFF%%
+#define HAS_TM_TM_GMTOFF
+#endif
+#if %%HAS_TM_TM_ZONE%%
+#define HAS_TM_TM_ZONE
+#endif
 
 
 /* USE_SYSTEM_LOCALTIME
    USE_SYSTEM_GMTIME
+   USE_SYSTEM_MKTIME
+   USE_SYSTEM_TIMEGM
    Should we use the system functions if the time is inside their range?
    Your system localtime() is probably more accurate, but our gmtime() is
    fast and safe.
 */
 #define USE_SYSTEM_LOCALTIME
 /* #define USE_SYSTEM_GMTIME */
-
-
-/* SYSTEM_LOCALTIME_MAX
-   SYSTEM_LOCALTIME_MIN
-   SYSTEM_GMTIME_MAX
-   SYSTEM_GMTIME_MIN
-   Maximum and minimum values your system's gmtime() and localtime()
-   can handle.  We will use your system functions if the time falls
-   inside these ranges.
-*/
-#define SYSTEM_LOCALTIME_MAX     %%localtime_max%%
-#define SYSTEM_LOCALTIME_MIN     %%localtime_min%%
-#define SYSTEM_GMTIME_MAX        %%gmtime_max%%
-#define SYSTEM_GMTIME_MIN        %%gmtime_min%%
+#define USE_SYSTEM_MKTIME
+/* #define USE_SYSTEM_TIMEGM */
 
 #endif /* TIME64_CONFIG_H */
@@ -0,0 +1,88 @@
+/* 
+   Maximum and minimum inputs your system's respective time functions
+   can correctly handle.  time64.h will use your system functions if
+   the input falls inside these ranges and corresponding USE_SYSTEM_*
+   constant is defined.
+*/
+
+#ifndef TIME64_LIMITS_H
+#define TIME64_LIMITS_H
+
+/* Max/min for localtime() */
+#define SYSTEM_LOCALTIME_MAX    %%localtime_max%%
+#define SYSTEM_LOCALTIME_MIN    %%localtime_min%%
+
+/* Max/min for gmtime() */
+#define SYSTEM_GMTIME_MAX       %%gmtime_max%%
+#define SYSTEM_GMTIME_MIN       %%gmtime_min%%
+
+/* Max/min for mktime() */
+#define SYSTEM_MKTIME_MAX_TM_SEC        %%mktime_max_tm_sec%%
+#define SYSTEM_MKTIME_MAX_TM_MIN        %%mktime_max_tm_min%%
+#define SYSTEM_MKTIME_MAX_TM_HOUR       %%mktime_max_tm_hour%%
+#define SYSTEM_MKTIME_MAX_TM_MDAY       %%mktime_max_tm_mday%%
+#define SYSTEM_MKTIME_MAX_TM_MON        %%mktime_max_tm_mon%%
+#define SYSTEM_MKTIME_MAX_TM_YEAR       %%mktime_max_tm_year%%
+#define SYSTEM_MKTIME_MAX_TM_WDAY       %%mktime_max_tm_wday%%
+#define SYSTEM_MKTIME_MAX_TM_YDAY       %%mktime_max_tm_yday%%
+#define SYSTEM_MKTIME_MAX_TM_ISDST      %%mktime_max_tm_isdst%%
+#ifdef HAS_TM_TM_ZONE
+    #define SYSTEM_MKTIME_MAX_TM_ZONE   "%%mktime_max_tm_zone%%"
+#endif
+#ifdef HAS_TM_TM_GMTOFF
+    #define SYSTEM_MKTIME_MAX_TM_GMTOFF %%mktime_max_tm_gmtoff%%
+#endif
+
+#define SYSTEM_MKTIME_MIN_TM_SEC        %%mktime_min_tm_sec%%
+#define SYSTEM_MKTIME_MIN_TM_MIN        %%mktime_min_tm_min%%
+#define SYSTEM_MKTIME_MIN_TM_HOUR       %%mktime_min_tm_hour%%
+#define SYSTEM_MKTIME_MIN_TM_MDAY       %%mktime_min_tm_mday%%
+#define SYSTEM_MKTIME_MIN_TM_MON        %%mktime_min_tm_mon%%
+#define SYSTEM_MKTIME_MIN_TM_YEAR       %%mktime_min_tm_year%%
+#define SYSTEM_MKTIME_MIN_TM_WDAY       %%mktime_min_tm_wday%%
+#define SYSTEM_MKTIME_MIN_TM_YDAY       %%mktime_min_tm_yday%%
+#define SYSTEM_MKTIME_MIN_TM_ISDST      %%mktime_min_tm_isdst%%
+#ifdef HAS_TM_TM_ZONE
+    #define SYSTEM_MKTIME_MIN_TM_ZONE   "%%mktime_min_tm_zone%%"
+#endif
+#ifdef HAS_TM_TM_GMTOFF
+    #define SYSTEM_MKTIME_MIN_TM_GMTOFF %%mktime_min_tm_gmtoff%%
+#endif
+
+
+/* Max/min for timegm() */
+#ifdef HAS_TIMEGM
+    #define SYSTEM_TIMEGM_MAX_TM_SEC        %%timegm_max_tm_sec%%
+    #define SYSTEM_TIMEGM_MAX_TM_MIN        %%timegm_max_tm_min%%
+    #define SYSTEM_TIMEGM_MAX_TM_HOUR       %%timegm_max_tm_hour%%
+    #define SYSTEM_TIMEGM_MAX_TM_MDAY       %%timegm_max_tm_mday%%
+    #define SYSTEM_TIMEGM_MAX_TM_MON        %%timegm_max_tm_mon%%
+    #define SYSTEM_TIMEGM_MAX_TM_YEAR       %%timegm_max_tm_year%%
+    #define SYSTEM_TIMEGM_MAX_TM_WDAY       %%timegm_max_tm_wday%%
+    #define SYSTEM_TIMEGM_MAX_TM_YDAY       %%timegm_max_tm_yday%%
+    #define SYSTEM_TIMEGM_MAX_TM_ISDST      %%timegm_max_tm_isdst%%
+    #ifdef HAS_TM_TM_ZONE
+        #define SYSTEM_TIMEGM_MAX_TM_ZONE   "%%timegm_max_tm_zone%%"
+    #endif
+    #ifdef HAS_TM_TM_GMTOFF
+        #define SYSTEM_TIMEGM_MAX_TM_GMTOFF %%timegm_max_tm_gmtoff%%
+    #endif
+    
+    #define SYSTEM_TIMEGM_MIN_TM_SEC        %%timegm_min_tm_sec%%
+    #define SYSTEM_TIMEGM_MIN_TM_MIN        %%timegm_min_tm_min%%
+    #define SYSTEM_TIMEGM_MIN_TM_HOUR       %%timegm_min_tm_hour%%
+    #define SYSTEM_TIMEGM_MIN_TM_MDAY       %%timegm_min_tm_mday%%
+    #define SYSTEM_TIMEGM_MIN_TM_MON        %%timegm_min_tm_mon%%
+    #define SYSTEM_TIMEGM_MIN_TM_YEAR       %%timegm_min_tm_year%%
+    #define SYSTEM_TIMEGM_MIN_TM_WDAY       %%timegm_min_tm_wday%%
+    #define SYSTEM_TIMEGM_MIN_TM_YDAY       %%timegm_min_tm_yday%%
+    #define SYSTEM_TIMEGM_MIN_TM_ISDST      %%timegm_min_tm_isdst%%
+    #ifdef HAS_TM_TM_ZONE
+        #define SYSTEM_TIMEGM_MIN_TM_ZONE   "%%timegm_min_tm_zone%%"
+    #endif
+    #ifdef HAS_TM_TM_GMTOFF
+        #define SYSTEM_TIMEGM_MIN_TM_GMTOFF %%timegm_min_tm_gmtoff%%
+    #endif
+#endif
+
+#endif /* TIME64_LIMITS_H */